﻿using ConfigurationSettings;
using FileSystemControls.Support;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;

namespace FileSystemControls.ListViewItems
{
    public class DirectoryListViewItem : FileSystemListViewItem
    {
        #region ** Member Variables **

        /// <summary>
        /// Internal representation of the DirectoryInfo object for this directory
        /// </summary>
        protected DirectoryInfo _thisDirectory;
        protected BackgroundWorker _backgroundWorker;

        #endregion

        #region ** Properties **

        public string ParentDirectory
        {
            get
            {
                return _thisDirectory.Parent != null ? _thisDirectory.Parent.FullName : null;
            }
        }

        /// <summary>
        /// Length of the file this item refers to.
        /// </summary>
        public new long Length
        {
            get
            {
                return _fileLength;
            }
            set
            {
                string displayFormat;

                _fileLength = value;
                decimal displayValue = _fileLength / (decimal)_sizeMultiplier;
                if (_sizePrecision == 0)
                {
                    displayFormat = "#,##0";
                }
                else
                {
                    displayFormat = "#,##0." + new string('0', _sizePrecision);
                }

                SubItems[1].Text = Math.Round(displayValue, _sizePrecision).ToString(displayFormat) + _sizePostfix;
            }
        }

        public override SizeUnitType FileSizeType
        {
            get
            {
                return _fileSizeType;
            }
            set
            {
                _fileSizeType = value;
                switch (_fileSizeType)
                {
                    case SizeUnitType.Bytes:
                        _sizeMultiplier = 1; //2^1
                        _sizePostfix = " b";
                        break;
                    case SizeUnitType.Kilobytes:
                        _sizeMultiplier = 1024; //2^10
                        _sizePostfix = " Kb";
                        break;
                    case SizeUnitType.Megabytes:
                        _sizeMultiplier = 1048576; //2^20
                        _sizePostfix = " Mb";
                        break;
                    case SizeUnitType.Gigabytes:
                        _sizeMultiplier = 1073741824; //2^30
                        _sizePostfix = " Gb";
                        break;
                }
            }
        }

        #endregion

        #region ** Constructors **

        protected DirectoryListViewItem()
        {
        }

        public DirectoryListViewItem(DirectoryInfo argCurrentEntry, bool argIsDotDot = false)
            : this()
        {
            NativeMethods.SHFILEINFO shinfo = new NativeMethods.SHFILEINFO();

            //Use this to get the small Icon
            //iconHandle = NativeMethods.SHGetFileInfo(argCurrentEntry.FullName, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), NativeMethods.SHGFI_ICON | NativeMethods.SHGFI_SMALLICON | NativeMethods.SHGFI_TYPENAME);
            NativeMethods.SHGetFileInfo(argCurrentEntry.FullName, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), NativeMethods.SHGFI_ICON | NativeMethods.SHGFI_SMALLICON | NativeMethods.SHGFI_TYPENAME);
            _smallIcon = System.Drawing.Icon.FromHandle(shinfo.hIcon);

            if (argCurrentEntry.Exists)
            {
                _isFile = false;

                SubItems.Add("<DIR>");
                Path = argCurrentEntry.FullName;

                _fullPath = argCurrentEntry.FullName;
                ((FileSystemListViewItem) this).RootName = argCurrentEntry.Name;
                _thisDirectory = argCurrentEntry;
                _isReadOnly = false;

                Name = argCurrentEntry.Name;
                _createDate = argCurrentEntry.CreationTime;
                _modifyDate = argCurrentEntry.LastWriteTime;

                //if (IsDotDot)
                //{
                //    Text = "..";
                //    SubItems.Add("..");
                //    _isDotDot = true;
                //}
                //else
                //{
                Text = argCurrentEntry.Name;
                SubItems.Add(argCurrentEntry.LastWriteTime.ToString());
                //_isDotDot = false;
                //}
            }
            else
            {
                throw new DirectoryNotFoundException();
            }

        }

        #endregion

        #region ** Public Methods **

        public override void Refresh()
        {
            _thisDirectory.Refresh();
        }

        public override void Rename(string argNewName)
        {
            Rename(argNewName, false);
        }

        public override void Rename(string argNewName, bool argForceAction)
        {
            if (Name == argNewName)
            {
                return;
            }

            if (_thisDirectory.Parent != null)
            {
                _thisDirectory.MoveTo(_thisDirectory.Parent.FullName + "\\" + argNewName);
            }
        }

        public void MoveTo(string argNewPath, bool argForceAction = false)
        {
            //TODO: 005 - Make this use the CopyProgress modal
            // better yet,change the CopyProgress to be a simple modal progress
            if (Path == argNewPath)
            {
                throw new PathMismatchException("Source and destination paths cannot be the same.");
            }

            //Create DestPath by concatinating NewPath and This path
            string destPath = argNewPath + "\\" + Name;

            if (!Directory.Exists(destPath))
            {
                Directory.CreateDirectory(destPath);
            } //TODO: 015 - ELSE ask to replace any files that conflict.

            List<FileSystemListViewItem> currentSubItems = GetSubItems();
            foreach (FileSystemListViewItem currentItem in currentSubItems)
            {
                FileListViewItem fileItem = currentItem as FileListViewItem;
                if (fileItem != null)
                {
                    fileItem.MoveTo(destPath, argForceAction);
                }

                DirectoryListViewItem dirItem = currentItem as DirectoryListViewItem;
                if (dirItem != null)
                {
                    dirItem.MoveTo(destPath, argForceAction);

                    //Delete the currentItem
                    //((DirectoryListViewItem)CurrentItem).Delete(ForceAction);
                }
            }

            Delete(argForceAction);
        }

        //public override void CopyTo(string argNewPath)
        //{
        //    CopyTo(argNewPath, false);
        //}

        //TODO: 012 - Enable multithreaded Copy support for Directories
        //TODO: 013 - Enable some sort of progress for copying a directory
        public override void CopyTo(string argNewPath, bool argForceAction = false)
        {
            //Check that NewPath is a path, not a file.
            if (File.Exists(argNewPath))
            {
                throw new ArgumentException("Can't copy directory to a file.");
            }

            //Check if NewPath + Dir already exists
            DirectoryInfo destinationDirectory = new DirectoryInfo(argNewPath + "\\" + Name);
            if (destinationDirectory.Exists && !argForceAction)
            {
                throw new DirectoryAlreadyExistsException(destinationDirectory);
            }

            //Call CopyDir for this directory
            //CopyDir(new DirectoryInfo(_fullPath), new DirectoryInfo(NewPath), ForceAction);
            CopyDir(_thisDirectory, destinationDirectory, argForceAction);

            OnCopyComplete();
        }

        public void Duplicate()
        {
            CopyDir(new DirectoryInfo(_fullPath), new DirectoryInfo(_thisDirectory.Parent.FullName + "\\Duplicate of " + _thisDirectory.Name), false);
        }

        public void Delete(bool argForceAction = false)
        {
            DeleteDir(_thisDirectory, argForceAction);
        }

        public void GetDirSizeAsync()
        {
            //BackgroundWorker has mechanisms to handle errors.
            if (_backgroundWorker == null)
            {
                //_backgroundWorker = new BackgroundWorker();
                _backgroundWorker = new CallbackBackgroundWorker();
                _backgroundWorker.DoWork += DirectorySizer_DoWork;
                _backgroundWorker.RunWorkerCompleted += DirectorySizer_RunWorkerCompleted;
                _backgroundWorker.WorkerReportsProgress = false;
                _backgroundWorker.WorkerSupportsCancellation = true;
            }
            else
            {
                if (_backgroundWorker.IsBusy)
                {
                    _backgroundWorker.CancelAsync();
                    while (_backgroundWorker.IsBusy)
                    { System.Windows.Forms.Application.DoEvents(); }
                }
            }

            SubItems[1].Text = @"working...";
            _backgroundWorker.RunWorkerAsync(_thisDirectory);

        }

        public List<FileSystemListViewItem> GetSubItems()
        {
            List<FileSystemListViewItem> tempList = new List<FileSystemListViewItem>();

            FileInfo[] fileList = _thisDirectory.GetFiles();
            foreach (FileInfo currentInfo in fileList)
            {
                tempList.Add(new FileListViewItem(currentInfo));
            }

            DirectoryInfo[] directoryList = _thisDirectory.GetDirectories();
            foreach (DirectoryInfo currentInfo in directoryList)
            {
                tempList.Add(new DirectoryListViewItem(currentInfo));
            }

            return tempList;
        }

        #endregion

        #region ** Protected and private methods **

        protected long GetDirSizeAsync(DirectoryInfo argCurrentDirectory, BackgroundWorker argCurrentWorker)
        {
            long directorySize = 0;

            if (argCurrentWorker.CancellationPending)
            {
                return -1;
            }
            //Try the GetFiles before actually looping through them in case I get an AccessDenied exception
            try
            {
                //Loop through FileList...
                foreach (FileInfo currentFile in argCurrentDirectory.GetFiles())
                {
                    if (argCurrentWorker.CancellationPending)
                    {
                        break;
                    }

                    //...and accumulate sizes
                    directorySize += currentFile.Length;
                }
            }
            catch (Exception)
            { directorySize += 0; }

            if (argCurrentWorker.CancellationPending)
            {
                return -1;
            }

            //Also, try the GetDirectories in case I get an AccessDenied exception.
            try
            {
                //Loop through subdirs...
                foreach (DirectoryInfo subDirectory in argCurrentDirectory.GetDirectories())
                {
                    if (argCurrentWorker.CancellationPending)
                    {
                        break;
                    }
                    //...and call GetDirSizeAsync(DirectoryInfo CurrentDirectory)
                    //...and acumulate sizes
                    directorySize += GetDirSizeAsync(subDirectory, argCurrentWorker);
                }
            }
            catch (Exception)
            { directorySize += 0; }

            if (argCurrentWorker.CancellationPending)
            {
                return -1;
            }

            return directorySize;

        }

        private void DirectorySizer_DoWork(object argSender, DoWorkEventArgs argEventArgs)
        {
            //Get the current worker thread so we can check for cancelation along the way.
            BackgroundWorker currentWorker = (BackgroundWorker)argSender;

            //Get the directory
            DirectoryInfo currentDirectory = argEventArgs.Argument as DirectoryInfo;

            _fileLength = GetDirSizeAsync(currentDirectory, currentWorker);
            if (currentWorker.CancellationPending)
            {
                argEventArgs.Cancel = true;
            }

        }

        private void DirectorySizer_RunWorkerCompleted(object argSender, RunWorkerCompletedEventArgs argEventArgs)
        {
            if (argEventArgs.Cancelled)
            {
                SubItems[1].Text = @"<DIR>";
                ListView.Invalidate(true);
                return;
            }

            //Nothing to do if and when it completes.
            Length = _fileLength;
            ListView.Invalidate(true);
        }

        #endregion

    }
}
